Skip to content

Tell workers to kill operations the scheduler no longer has executing on them - #2693

Merged
amankrx merged 4 commits into
TraceMachina:mainfrom
amankrx:fix/scheduler-kill-operation
Aug 20, 2026
Merged

Tell workers to kill operations the scheduler no longer has executing on them#2693
amankrx merged 4 commits into
TraceMachina:mainfrom
amankrx:fix/scheduler-kill-operation

Conversation

@amankrx

@amankrx amankrx commented Aug 16, 2026

Copy link
Copy Markdown
Member

What and why

The scheduler never sent KillOperationRequest (only tests constructed one), so when an operation was finished server-side (client timeout, cancellation, execution deadline, requeue) the worker kept running it and holding its slot until the process ended on its own. This adds a periodic pass that asks the state manager whether each running operation is still executing on its worker and tells the worker to kill it if not. The worker's later report for a killed operation is not forwarded, so the kill's error cannot burn a retry on a re-queued copy.

How was this verified?

Unit tests only. is_executing_on_worker is table-tested across queued, assigned, requeued, reassigned, completed and unknown. A scheduler-level test drives a real client timeout while a single-slot worker holds the operation and checks that exactly one kill is sent, the worker's report returns Ok, and the slot frees for the next action; without the change, no kill is sent and the report fails with "already completed". A third test drops the worker channel and checks the worker is evicted when the kill cannot be delivered. Not yet run against a real deployment.

Risk

Behaviour change: an operation whose client vanished is now killed instead of running to completion and warming the AC. Every 5s each running operation costs one state-manager lookup, which is one Redis GET on store-backed deployments. New trait method on WorkerStateManager and WorkerScheduler. Wire format unchanged, the message already existed.


This change is Reviewable

@vercel

vercel Bot commented Aug 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nativelink Ready Ready Preview Aug 20, 2026 11:28pm
nativelink-aidm Ready Ready Preview Aug 20, 2026 11:28pm

Request Review

@amankrx

amankrx commented Aug 20, 2026

Copy link
Copy Markdown
Member Author

/build-image nativelink-worker-init

@amankrx

amankrx commented Aug 20, 2026

Copy link
Copy Markdown
Member Author

/build-image nativelink

@github-actions

Copy link
Copy Markdown
Contributor

Image built and pushed!

ghcr.io/TraceMachina/nativelink-worker-init:2026-08-20-5d29f8266c836872a9bb278787063548ee1d1bd8

@github-actions

Copy link
Copy Markdown
Contributor

Image built and pushed!

ghcr.io/TraceMachina/nativelink:2026-08-20-5d29f8266c836872a9bb278787063548ee1d1bd8

Comment thread nativelink-scheduler/src/api_worker_scheduler.rs

@MarcusSorealheis MarcusSorealheis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have asked a clanker to review this one.

@MarcusSorealheis MarcusSorealheis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read through the whole change and traced it against the source — the mechanism is sound and the test coverage is genuinely strong (the is_executing_on_worker table-test across queued/assigned/requeued/reassigned/finished/unknown, plus the undeliverable-kill → evict test). A few notes inline on lock scope and Redis load.

One design-level point that doesn't map to a single line: this is a global, unconfigurable behaviour change. Previously an orphaned-but-running action ran to completion and warmed the action cache, so a later identical request hit cache; now it's killed, discarding in-flight work and the would-be cache entry. For long / expensive actions whose client merely dropped and will retry, that converts a cache hit into a full re-execution. Worth gating behind a config knob so operators with expensive long-running actions can opt out — the default is a policy call, but there should be an escape hatch.

(Not a blocker — CI is green and the core logic looks correct. Inline comments are suggestions.)

return Ok(());
}

let mut inner = self.inner.lock().await;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lock held across store I/O. Phase 3 re-checks is_executing_on_worker (L1012) while holding self.inner — unlike phase 1, which correctly snapshots under the lock and does the store calls lock-free. This sweep fires during timeout/disconnect storms (many revoked ops at once), so the global scheduler mutex ends up held across N sequential state-manager round-trips (a Redis GET each on store-backed deployments), serializing all worker updates and matching behind network I/O.

Suggest keeping the phase-3 recheck lock-free (mirroring phase 2), collecting the confirmed set, then taking the lock only for the worker_notify_kill_operation sends. worker_notify_kill_operation already re-guards with contains_key + is_kill_requested, so the TOCTOU window stays tight without holding the lock across the store call.

Comment thread nativelink-service/src/worker_api_server.rs Outdated
worker
.running_action_infos
.iter()
.filter(|(_, pending_action_info)| !pending_action_info.kill_requested)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slot-leak corner for a live-but-unresponsive worker. Once kill_requested is set, the op is filtered out of every future sweep here, and nothing else re-sends or times out the kill itself. A dead worker is recovered by remove_timedout_workers, but a live worker that drops or ignores the KillOperationRequest holds its slot indefinitely while update_action silently swallows its updates (L377). Worth a comment noting that keepalive-timeout eviction is the sole recovery path here, or a bounded re-send.

self.id
)
})?;
pending_action_info.kill_requested = true;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: kill_requested is set before send_msg_to_worker can fail just below. It's safe in practice because the caller (worker_notify_kill_operation) evicts + requeues on send failure, discarding this entry — but "flag set, message not delivered" reads like a latent slot leak. A one-line note that the flag's lifetime is bounded by eviction-on-send-failure would help the next reader.

@amankrx

amankrx commented Aug 20, 2026

Copy link
Copy Markdown
Member Author

/build-image nativelink-worker-init

@amankrx

amankrx commented Aug 20, 2026

Copy link
Copy Markdown
Member Author

/build-image nativelink

@github-actions

Copy link
Copy Markdown
Contributor

Image built and pushed!

ghcr.io/TraceMachina/nativelink:2026-08-20-8ae33172a598687291b672d1ef2a1c7a2d1e967c

@github-actions

Copy link
Copy Markdown
Contributor

Image built and pushed!

ghcr.io/TraceMachina/nativelink-worker-init:2026-08-20-8ae33172a598687291b672d1ef2a1c7a2d1e967c

@amankrx
amankrx force-pushed the fix/scheduler-kill-operation branch from 8889c0f to d7394cd Compare August 20, 2026 23:26
@amankrx
amankrx merged commit a851147 into TraceMachina:main Aug 20, 2026
45 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants